import { NodeRequestException } from './requestException.ts';
import { NodeResponseException } from './responseException.ts';
import type { BaseRequestOption, DefaultRequestOption, RetryRequestOption } from './types';

const handleQuery = (url: string, query: Record<string, any>) => {
  const keyList = Object.keys(query) as string[];
  if (keyList.length) {
    const paramString = keyList.reduce((pre, cur) => {
      return pre + `${cur}=${query[cur]}&`;
    }, '?');
    url += paramString;
  }
  return url;
};

class NodeRequest {
  timeout: number;

  /**
   * 构造一个 NodeRequest 实例。
   * @param timeout 可选。请求超时时间（毫秒）。另外支持在单个请求的 options 中设置超时时间。
   */
  constructor(timeout?: number) {
    this.timeout = timeout || 10000;
  }

  /**
   * 发起通用 HTTP 请求。
   * @param url 请求 URL。
   * @param options 请求选项。
   * @returns 返回一个 Promise，解析为请求结果。
   * @exception {NodeResponseException, NodeRequestException}  请求/响应错误。
   */
  request<T = any>(url: string, options: BaseRequestOption) {
    const abortController = new AbortController();

    const timerId = setTimeout(() => {
      abortController.abort();
    }, options.timeout || this.timeout);

    const requestInit = {
      headers: {
        'Content-Type': 'application/json',
      },
      signal: abortController.signal,
    };

    if (options.headers) {
      requestInit.headers = Object.assign(requestInit.headers, options.headers);
    }

    return new Promise<T>((resolve, reject) => {
      fetch(url, requestInit)
        .then((response) => {
          if (response.ok) {
            clearTimeout(timerId);
            response.json().then((data) => resolve(data));
          } else {
            // 将响应错误转换为自定义异常，并且包装原始错误对象返回，便于下一层判断具体错误类型返回给起前端
            reject(new NodeResponseException(response.statusText, response.status, response));
          }
        })
        .catch((error) => {
          if (error.name === 'AbortError') {
            reject(new NodeRequestException('TIME_OUT', '被请求的服务器响应超时，本次请求已取消'));
          } else {
            reject(new NodeRequestException('NET_OR_SERVER_DOWN', '网络错误或服务器无法访问'));
          }
        });
    });
  }

  /**
   * 发起 GET 请求。
   * @param url 请求 URL。
   * @param query 可选。请求查询参数。
   * @param options 可选。请求选项，不包括 body。
   * @returns 返回一个 Promise，解析为请求结果。
   */
  get<T = any>(
    url: string,
    query?: Record<string, any>,
    options?: Omit<DefaultRequestOption, 'body'>,
  ) {
    query && (url = handleQuery(url, query));
    return this.request<T>(url, {
      method: 'GET',
      ...options,
    });
  }

  /**
   * 发起 HEAD 请求。
   * @param url 请求 URL。
   * @param query 可选。请求查询参数。
   * @param options 可选。请求选项，不包括 body。
   * @returns 返回一个 Promise，解析为请求结果。
   */
  head<T = any>(
    url: string,
    query?: Record<string, any>,
    options?: Omit<DefaultRequestOption, 'body'>,
  ) {
    query && (url = handleQuery(url, query));
    return this.request<T>(url, {
      method: 'HEAD',
      ...options,
    });
  }

  /**
   * 发起 POST 请求。
   * @param url 请求 URL。
   * @param data 可选。请求体数据。
   * @param options 可选。请求选项。
   * @returns 返回一个 Promise，解析为请求结果。
   */
  post<T = any>(url: string, data?: Record<string, any>, options?: DefaultRequestOption) {
    return this.request<T>(url, {
      method: 'POST',
      body: data ? JSON.stringify(data) : null,
      ...options,
    });
  }

  /**
   * 发起 PUT 请求。
   * @param url 请求 URL。
   * @param data 可选。请求体数据。
   * @param options 可选。请求选项。
   * @returns 返回一个 Promise，解析为请求结果。
   */
  put<T = any>(url: string, data?: Record<string, any>, options?: DefaultRequestOption) {
    return this.request<T>(url, {
      method: 'PUT',
      body: data ? JSON.stringify(data) : null,
      ...options,
    });
  }

  /**
   * 发起 PATCH 请求。
   * @param url 请求 URL。
   * @param data 可选。请求体数据。
   * @param options 可选。请求选项。
   * @returns 返回一个 Promise，解析为请求结果。
   */
  patch<T = any>(url: string, data?: Record<string, any>, options?: DefaultRequestOption) {
    return this.request<T>(url, {
      method: 'PATCH',
      body: data ? JSON.stringify(data) : null,
      ...options,
    });
  }

  /**
   * 发起 DELETE 请求。
   * @param url 请求 URL。
   * @param query 可选。请求查询参数。
   * @param data 可选。请求体数据。
   * @param options 可选。请求选项。
   * @returns 返回一个 Promise，解析为请求结果。
   */
  delete<T = any>(
    url: string,
    query?: Record<string, any>,
    data?: Record<string, any>,
    options?: DefaultRequestOption,
  ): Promise<T> {
    query && (url = handleQuery(url, query));
    return this.request<T>(url, {
      method: 'HEAD',
      body: data ? JSON.stringify(data) : null,
      ...options,
    });
  }

  /**
   * 在请求失败时重试；慎用此方法，并且建议设置较短的超时时间，超时时间过长通常会是前端先超时。
   * @param url 请求 URL。
   * @param query 可选。请求查询参数。
   * @param data 可选。请求体数据。
   * @param options 可选。重试请求选项。
   * @returns 返回一个 Promise，解析为请求结果。
   */
  async retry<T = any>(
    url: string,
    query?: Record<string, any>,
    data?: Record<string, any>,
    options: RetryRequestOption = {
      method: 'GET',
      retryCount: 3,
    },
  ) {
    query && (url = handleQuery(url, query));
    let resp: T | null = null;

    for (let i = 0; i < options.retryCount; i++) {
      try {
        resp = await this.request<T>(
          url,
          Object.assign(options, {
            body: data ? JSON.stringify(data) : null,
          }),
        );
        break;
      } catch (error) {
        if (i === options.retryCount - 1) {
          throw error;
        }
      }
    }
    return resp;
  }
}
export { NodeRequest };
